package ro.flashbite.loaders.types
{	
	import flash.display.Bitmap;
	import flash.display.Loader;
	import flash.events.*;
	import flash.media.Sound;
	import flash.media.SoundLoaderContext;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.system.ApplicationDomain;
	import flash.utils.getTimer;
	
	import ro.flashbite.helpers.MathHelpers;
	import ro.flashbite.interfaces.IInitDestroyAsset;
	import ro.flashbite.loaders.events.AssetEvent;
	import ro.flashbite.loaders.types.consts.AssetStatuses;
	import ro.flashbite.loaders.types.consts.AssetTypes;
	import ro.flashbite.loggers.ILogger;
	import ro.flashbite.loggers.LoggerFactory;
	
	/**
	 *
	 * This is the base class of an asset(text, xml, swf and mp3 for now); It will load data; assisted by a Manager, or by himself
	 *  
	 * @author Adrian Barbu
	 * 
	 */	
	public class Asset extends EventDispatcher implements IInitDestroyAsset
	{	
		/**
		 * current status of the asset 
		 */		
		private var 		_status				:	int;
		/**
		 * data contained in this asset 
		 */		
		protected var 		_content			:	*;
		/**
		 * appDom of of swf(onSwfAsset)
		 */		
		protected var		_appDom				:	ApplicationDomain;
		/**
		 * the type of this asset - AssetConsts. 
		 */		
		private var 		_type				:	String;
		/**
		 * this is the unique name of this asset 
		 */		
		protected var 		_id					:	String;
		/**
		 * this is the location from were we load the asset content 
		 */		
		private var 		_urlReq				:	URLRequest;
		/**
		 * asset class name, used for debug purpose 
		 */		
		protected var		_loggerName			:	String;
		/**
		 * used for logging messages 
		 */		
		protected var 		_logger				:	ILogger;
		/**
		 * loader who is in charge of events 
		 */		
		protected var 		_loaderDispacher	:	IEventDispatcher;
		/**
		 * the loader who loads data 
		 */		
		protected var 		_loader				:	*;
		/**
		 * bytes loaded at some point 
		 */		
		protected var 		_bytesLoaded		:	Number;
		/**
		 * total number of bytes of the asset 
		 */		
		protected var 		_bytesTotal			:	Number;
		/**
		 * diference between _realStartTime and _startTime
		 */		
		private var 		_delayBeforeLoad	:	Number;
		/**
		 * the loading time of asset
		 */		
		private var 		_loadingTime		:	Number;
		/**
		 * the starting time of a asset
		 */		
		private var			_startTime			:	Number;
		/**
		 * the real start time of a asset; if on web, http request is about 50-200 ms
		 */		
		private var			_realStartTime		:	Number;
		
		//==================================================================================================
		// CONSTRUCTOR
		//==================================================================================================
		
		public function Asset() { }
		
		//==================================================================================================
		// PUBLIC
		//==================================================================================================
		
		/**
		 *
		 * Proper constructor - used for code performance
		 *  
		 * @param type = asset type - from AssetConsts
		 * @param id = unique name of the asset
		 * @param url = location from were we load the asset
		 * @param fromManager = boolean that knows if a manager handle this asset; if not, verify data received
		 * @param forceReload = boolean that sets a unique identifier to urlPath to force reloading, even if the asset is cached
		 * 
		 */		
		public final function init(type:String, id:String, urlPath:String, fromManager:Boolean = true, forceReload:Boolean = false):void
		{
			setLoggerName();
			
			_logger = LoggerFactory.getLogger(_loggerName);
			
			if (!fromManager) {
				verifyType(type);
			} 
			_type = type;
			_id = id;
			if (forceReload) urlPath = urlPath + "?" + new Date().toString();
			_urlReq = new URLRequest(urlPath);
			
			_status = AssetStatuses.STOPPED;
			
			setLoaderAndDispacher();
			
			if (!_type || !_id || !_urlReq) 
				throw new Error("Could not set this Asset : _type = " + _type + ", _id = " + _id + ", _url" + _urlReq);
		}
		
		/**
		 *
		 * This is the function were asset load is started 
		 * 
		 */		
		public function startLoad():void
		{	
			if (_loader && _loaderDispacher) {
				addListenersToLoader(_loaderDispacher);
				_startTime = getTimer();
				if (_loader is Sound) {
					(_loader as Sound).load(_urlReq, new SoundLoaderContext(1000, true)); 
				} else _loader.load(_urlReq);
			} else _logger.error("Couldn't start loading. Loader or LoaderDispacher is null...");
		}
		
		/**
		 * 
		 * Creates a info of loading
		 * 
		 * @return a string with loadingInfo
		 * 
		 */		
		public function getLoadingInfo():String
		{
			var info					: String 	= "|" + _id + "| ",
				speed					: Number,
				roundToDecimalFunction 	: Function 	= MathHelpers.roundToDecimals/*,
				bytesToKilobytesFunction: Function	= MathHelpers.bytesToKb*/;
				
			if (_loadingTime <= 0) {
				speed = roundToDecimalFunction(/*bytesToKilobytesFunction(*/_bytesTotal/*)*//1*1000 / 1024, 3);
			} else speed = roundToDecimalFunction(/*bytesToKilobytesFunction(*/_bytesTotal/*)*//_loadingTime * 1000 / 1024, 3);
			
			info   += "Size : " 	+ 	roundToDecimalFunction(/*bytesToKilobytesFunction(*/_bytesTotal/*)*/ / 1024, 3)	+ " Kb |-| " 		
					+ "Speed : " 	+ 	speed																			+ " Kb/Sec |-| " 	
					+ "Delay : " 	+ 	_delayBeforeLoad																+ " ms |-| "
					+ "Duration : "	+ 	_loadingTime																	+ " ms.";
			
			//_logger.debug(info);
			
			return info;
		}
		
		/**
		 *
		 * proper destroy 
		 * 
		 */		
		public function destroy():void
		{
			if (_loaderDispacher) {
				removeListenersFromLoader(_loaderDispacher);
				_loaderDispacher = null;
			}
			
			if (_content) {
				if (_content is Bitmap) Bitmap(_content).bitmapData.dispose();
				_content = null;
			}
			_urlReq = null;
			_appDom = null;
			_type = null;
			_id = null;
			
			if (_loader) {
				try {
					if (_loader is Loader) {
						(_loader as Loader).unloadAndStop();
					} else if (_loader is URLLoader || _loader is Sound) {
						_loader.close();
					} 
				} catch (e:Error) { /* _logger.error("Couldn't unload asset : " + _id); _logger.error(e.message); */ }
				
				_loader = null;
			}
			
			_loggerName = null;
			_logger = null;
		}
		
		//==================================================================================================
		// PROTECTED - to be overrided
		//==================================================================================================
		
		/**
		 *
		 * Sets the logger name; debug purpose 
		 * 
		 */		
		protected function setLoggerName():void
		{
			throw new Error("Override this, you must set internal name");
			
			//_className = NAME;
		}
		
		/**
		 * 
		 * Sets the eventDispacher of the loader
		 * 
		 */		
		protected function setLoaderAndDispacher():void
		{
			throw new Error("Override this, you must set the loaderDispacher");
			
			//_loader = new Loader()....
			//_loaderDispacher = _loader.contentLoaderInfo;
		}
		
		/**
		 * 
		 * @param e
		 * 
		 */		
		protected function onStarted(e:Event):void
		{
			_realStartTime = getTimer();
			_delayBeforeLoad = _realStartTime - _startTime;
			
			//_logger.debug(_id + " started...");
			_status = AssetStatuses.STARTED;
			
			dispatchEvent(e);
		}
		
		/**
		 * 
		 * @param e
		 * 
		 */		
		protected function onProgress(e:ProgressEvent):void
		{
			_bytesTotal = e.bytesTotal;
			_bytesLoaded = e.bytesLoaded;
			
			//_logger.debug(_id + " progress..." + MathHelpers.roundToSpecificDecimal(_bytesLoaded/_bytesTotal * 100, 2) + "%");
			
			dispatchEvent(e);
		}
		
		/**
		 * 
		 * @param e
		 * 
		 */		
		protected function onComplete(e:Event):void
		{
			_loadingTime = getTimer() - _realStartTime;
			
			//_logger.debug(_id + " completed...");
			_status = AssetStatuses.COMPLETED;
			
			dispatchEvent(e);
		}
		
		/**
		 * 
		 * @param e
		 * 
		 */		
		protected function onIOError(e:IOErrorEvent):void
		{
			//_logger.error(_id + " IO Error...");
			_status = AssetStatuses.ERROR;
			
			dispatchEvent(e);
		}
		
		/**
		 * 
		 * @param e
		 * 
		 */		
		protected function onSecurityError(e:SecurityErrorEvent):void
		{	
			//_logger.error(_id + " Security Error...");
			_status = AssetStatuses.ERROR;
			
			dispatchEvent(e);
		}
		
		/**
		 * 
		 * @param e
		 * 
		 */		
		protected function onHttpStatus(e:HTTPStatusEvent):void
		{
			//_logger.debug(_id + " Http Status..." + e.status);
			_status = AssetStatuses.ERROR;
			
			dispatchEvent(e);
		}
		
		override public function dispatchEvent(e:Event):Boolean
		{
			switch (e.type) {
				case Event.OPEN:
					super.dispatchEvent(new AssetEvent(AssetEvent.OPEN, e));
					break;
				case ProgressEvent.PROGRESS:
					super.dispatchEvent(new AssetEvent(AssetEvent.PROGRESS, e));
					break;
				case Event.COMPLETE:
					super.dispatchEvent(new AssetEvent(AssetEvent.COMPLETE, e));
					break;
				case IOErrorEvent.IO_ERROR:
					super.dispatchEvent(new AssetEvent(AssetEvent.IO_ERROR, e));
					break;
				case SecurityErrorEvent.SECURITY_ERROR:
					super.dispatchEvent(new AssetEvent(AssetEvent.SECURITY_ERROR, e));
					break;
				case HTTPStatusEvent.HTTP_STATUS:
					super.dispatchEvent(new AssetEvent(AssetEvent.HTTP_STATUS, e));
					break;
				default:
					_logger.error("Unknown event Type" + e.type);
					break;
			}
			
			return false;
		}
		
		//==================================================================================================
		// PROTECTED - to be used by children
		//==================================================================================================
		
		/**
		 * 
		 * Add listeners for loading process
		 * 
		 * @param loader
		 * 
		 */		
		protected final function addListenersToLoader(loaderDispacher:IEventDispatcher):void
		{	
			loaderDispacher.addEventListener(Event.OPEN, 						onStarted, 			false, 0, false	);
			loaderDispacher.addEventListener(ProgressEvent.PROGRESS, 			onProgress, 		false, 0, false	);
			loaderDispacher.addEventListener(Event.COMPLETE, 					onComplete, 		false, 0, false	);
			
			loaderDispacher.addEventListener(IOErrorEvent.IO_ERROR, 			onIOError, 			false, 0, false	);
			loaderDispacher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError, 	false, 0, false	);
			loaderDispacher.addEventListener(HTTPStatusEvent.HTTP_STATUS, 		onHttpStatus, 		false, 0, false	);
		}
		
		/**
		 * 
		 * Removes listeners for loading process
		 * 
		 * @param loader
		 * 
		 */
		protected final function removeListenersFromLoader(loaderDispacher:IEventDispatcher):void
		{
			loaderDispacher.removeEventListener(Event.OPEN, 						onStarted		);
			loaderDispacher.removeEventListener(ProgressEvent.PROGRESS, 			onProgress		);
			loaderDispacher.removeEventListener(Event.COMPLETE, 					onComplete		);
			
			loaderDispacher.removeEventListener(IOErrorEvent.IO_ERROR, 				onIOError		);
			loaderDispacher.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, 	onSecurityError	);
			loaderDispacher.removeEventListener(HTTPStatusEvent.HTTP_STATUS, 		onHttpStatus	);
		}
		
		//==================================================================================================
		// GETTERS
		//==================================================================================================
		
		public function getId()					: String			{ return _id; 				}
		public function getType()				: String			{ return _type; 			}
		public function getContent()			: * 				{ return _content; 			}
		public function getUrlReq()				: URLRequest 		{ return _urlReq;	 		}
		public function getStatus()				: int 				{ return _status; 			}
		public function getLoaderDispacher()	: IEventDispatcher 	{ return _loaderDispacher; 	}
		public function getAppDom()				: ApplicationDomain { return _appDom; 			}	
		public function getLoadingDelay()		: uint 				{ return _delayBeforeLoad; 	}	
		
		public function getBytesLoaded()		: uint 				{ return _bytesLoaded;		}	
		public function getBytesTotal()			: uint 				{ return _bytesTotal; 		}	
		
		//==================================================================================================
		// PRIVATE
		//==================================================================================================
		
		/**
		 *
		 * Verify if type given is known
		 *  
		 * @param type
		 * 
		 */		
		private function verifyType(type:String):void
		{
			if (!AssetTypes.ARR_TYPES.indexOf(type) == -1)
				throw new Error("Specified type " + type + " is unrecognized...");
		}
	}
}